package com.ccp.dev.workflow.service;

import com.ccp.dev.core.basic.base.BaseService;
import com.ccp.dev.core.basic.engine.GroovyScriptEngine;
import com.ccp.dev.core.util.BeanUtils;
import com.ccp.dev.core.util.StringPool;
import com.ccp.dev.core.util.StringUtil;
import com.ccp.dev.system.model.SysUser;
import com.ccp.dev.workflow.bpmconst.BpmConst;
import com.ccp.dev.workflow.dao.BpmNodeUserDao;
import com.ccp.dev.workflow.dao.BpmUserConditionDao;
import com.ccp.dev.workflow.model.BpmNodeSet;
import com.ccp.dev.workflow.model.BpmNodeUser;
import com.ccp.dev.workflow.model.BpmUserCondition;
import com.ccp.dev.workflow.model.ProcessRun;
import com.ccp.dev.workflow.model.bpm.*;
import com.ccp.dev.workflow.service.bpm.BpmNodeUserCalculationSelector;
import com.ccp.dev.workflow.service.bpm.IBpmNodeUserCalculation;
import org.apache.commons.lang.time.DateUtils;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.text.ParseException;
import java.util.*;

/**
 * ClassDescribe:
 * 流程节点人员配置信息服务类
 *
 * @author :wangcheng
 * Date: 2019-06-19
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class BpmNodeUserService extends BaseService<BpmNodeUser> {
    @Resource
    private BpmNodeUserDao bpmNodeUserDao;
    @Resource
    private BpmNodeSetService bpmNodeSetService;
    @Resource
    private BpmUserConditionDao bpmUserConditionDao;
    @Resource
    private BpmUserConditionService bpmUserConditionService;
    @Resource
    private BpmNodeUserCalculationSelector bpmNodeUserCalculationSelector;
    @Resource
    private ActDefModelService actDefModelService;
    @Resource
    private GroovyScriptEngine groovyScriptEngine;

    /**
     * 获取预览用户。
     * <pre>
     * 如果算法不支持预览的话，那么返回null。
     * </pre>
     *
     * @param bpmNodeUser   节点人员设置对象
     * @param startUserId   发起人
     * @param preTaskUserId 上一个节点执行人
     * @param procInstId    流程实例id
     * @param vars          流程变量
     * @return 用户列表
     */
    public List<SysUser> queryPreviewNodeUser(BpmNodeUser bpmNodeUser, String startUserId, String preTaskUserId, String procInstId, Map<String, Object> vars) {
        CalcVars params = new CalcVars(startUserId, preTaskUserId, procInstId, vars);
        IBpmNodeUserCalculation calculation = bpmNodeUserCalculationSelector.getByKey(bpmNodeUser.getAssignType());
        if (calculation.supportPreView()) {
            return calculation.getExecutor(bpmNodeUser, params);
        }
        return Collections.emptyList();
    }

    /**
     * 获取流程节点用户设置
     *
     * @param conditionId 条件id
     * @return 任务设置列表
     */
    public List<BpmNodeUser> getByConditionId(String conditionId) {
        return bpmNodeUserDao.getByConditionId(conditionId);
    }

    /**
     * 计算发起人启动某个流程后，`。
     * <pre>
     * 	返回用户数据包括流程参与者和通知人。
     *  1.参与者键为PARTICIPATION。BpmNodeUser.USER_TYPE_PARTICIPATION
     *  2.通知人键为NOTIFY。BpmNodeUser.USER_TYPE_NOTIFY
     * </pre>
     *
     * @param procDefId   流程定义id
     * @param procInstId  流程实例id
     * @param nodeId      节点id
     * @param startUserId 发起人
     * @return 执行的用户ID列表
     */
    public List getExeUserIds(String procDefId, String procInstId, String nodeId, String startUserId, String preTaskUser, Map<String, Object> vars) {
        List list = new ArrayList();
        String parentProcDefId = (String) vars.get(BpmConst.FLOW_PARENT_PROCDEFID);
        BpmNodeSet bpmNodeSet;
        if (StringUtil.isEmpty(parentProcDefId)) {
            bpmNodeSet = bpmNodeSetService.getByProcDefIdNodeIdOrParentProcDefId(nodeId, procDefId, null);
        } else {
            bpmNodeSet = bpmNodeSetService.getByProcDefIdNodeIdOrParentProcDefId(nodeId, procDefId, parentProcDefId);
        }
        if (bpmNodeSet == null) {
            return list;
        }
        //根据setId获取条件列表。
        List<BpmUserCondition> bpmUserConditionList = bpmUserConditionDao.queryBySetId(bpmNodeSet.getSetId());
        if (BeanUtils.isEmpty(bpmUserConditionList)) {
            return list;
        }

        list = getExecutorsByConditions(bpmUserConditionList, procDefId, procInstId, startUserId, preTaskUser, vars, false);

        return list;
    }

    /**
     * 根据条件列表，流程定义ID,流程实例ID,发起人ID,上个执行人ID,流程变量,是否取得所有的用户。
     *
     * @param bpmUserConditionList 条件列表
     * @param procDefId            流程定义id
     * @param procInstId           流程实例id
     * @param startUserId          发起人id
     * @param preTaskUser          上一个任务执行人
     * @param vars                 流程变量
     * @param isUser               是否直接抽取用户，这个在抄送，消息节点人员获取时使用。
     * @return 执行人列表
     */
    private List getExecutorsByConditions(List<BpmUserCondition> bpmUserConditionList,
                                          String procDefId, String procInstId, String startUserId, String preTaskUser, Map<String, Object> vars, boolean isUser) {
        String parentProcDefId = (String) vars.get(BpmConst.FLOW_PARENT_PROCDEFID);
        String formIdentity;
        if (StringUtil.isEmpty(parentProcDefId)) {
            formIdentity = getFormIdentity(procDefId);
        } else {
            formIdentity = getFormIdentity(procDefId, parentProcDefId);
        }
        List list = new ArrayList<>();
        //排序
        Collections.sort(bpmUserConditionList);

        Set userSet = new LinkedHashSet<>();
        //是否找到了执行人
        boolean hasExecutor = false;

        BpmUserCondition prevCondition = null;
        for (BpmUserCondition currentCondition : bpmUserConditionList) {
            String conditionId = currentCondition.getId();
            //上一个规则不为空 ,并且前面的批次和当前的批次不一样 且之前的规则找到用户了，这个时候跳出循环。
            if (prevCondition != null && !prevCondition.getGroupNo().equals(currentCondition.getGroupNo()) && hasExecutor) {
                break;
            }
            //检查条件
            boolean isPass = conditionCheck(currentCondition, formIdentity, vars);
            if (!isPass) {
                continue;
            }
            //取得执行人
            List partUsers = getExecutors(conditionId, procInstId, startUserId, preTaskUser, vars, isUser);
            if (!partUsers.isEmpty()) {
                userSet.addAll(partUsers);
                hasExecutor = true;
            }
            prevCondition = currentCondition;
        }
        list.addAll(userSet);
        return list;
    }

    /**
     * 根据流程定义id获取表单标识。
     *
     * @param procDefId 流程定义id
     * @return 表单标识
     */
    private String getFormIdentity(String procDefId) {
        return getFormIdentity(procDefId, null);
    }

    /**
     * 根据流程定义id获取表单标识。
     *
     * @param procDefId      流程定义id
     * @param parentActDefId 父流程定义id
     * @return 表单标识
     */
    private String getFormIdentity(String procDefId, String parentActDefId) {
        String formIdentity = "";
        BpmFormResult bpmFormResult = actDefModelService.getBpmFormResult(procDefId, parentActDefId);
        if (bpmFormResult.getResult() == 0) {
            formIdentity = bpmFormResult.getTableName();
        }
        return formIdentity;
    }

    /**
     * 计算规则是否成立
     *
     * @param currentCondition 规则实体
     * @param formIdentity     表名称
     * @param formVars         流程变量
     * @return 规则是否成立
     */
    public Boolean conditionCheck(BpmUserCondition currentCondition, String formIdentity, Map<String, Object> formVars) {
        Boolean isPassCondition;
        String tmpId = currentCondition.getFormIdentity();
        tmpId = tmpId == null ? "" : tmpId;
        //条件没有表单标识或者当前的标识和之前设置的标识不一致时，则认为规则 成立
        if (!tmpId.equals(formIdentity)) {
            return true;
        }

        if (StringUtil.isEmpty(currentCondition.getCondition())) {
            return true;
        }

        List<Map<String, Object>> operatorList = new ArrayList<>();
        List<ConditionJsonStruct> conditions = currentCondition.getConditionJson();
        getConditionResult(conditions, operatorList, formVars);
        //根据表单规则判断当前表单是否符合规则
        isPassCondition = executeOperator(operatorList);

        return isPassCondition;
    }

    /**
     * 计算规则结果
     *
     * @param conditions   规则列表
     * @param operatorList 结果集合
     * @param formVars     流程变量
     */
    private void getConditionResult(List<ConditionJsonStruct> conditions, List<Map<String, Object>> operatorList, Map<String, Object> formVars) {
        for (ConditionJsonStruct cond : conditions) {
            //组合规则
            if (cond.getBranch()) {
                List<Map<String, Object>> branchResultList = new ArrayList<>();
                getConditionResult(cond.getSub(), branchResultList, formVars);
                Boolean branchResult = executeOperator(branchResultList);
                Map<String, Object> resultMap = new HashMap<>(2);
                resultMap.put("result", branchResult);
                resultMap.put("operator", cond.getCompType());
                operatorList.add(resultMap);
            } else {
                Object formVal = formVars.get(cond.getFlowvarKey());
                if (Integer.valueOf(2).equals(cond.getRuleType())) {
                    String script = cond.getScript();
                    if (StringUtil.isNotEmpty(script)) {
                        boolean result = groovyScriptEngine.executeBoolean(script, formVars);
                        Map<String, Object> resultMap = new HashMap<>(2);
                        resultMap.put("operator", cond.getCompType());
                        resultMap.put("result", result);
                        operatorList.add(resultMap);
                    }
                } else {
                    String script = "";
                    switch (cond.getOptType()) {
                        case 1:
                            //数字
                            if (BeanUtils.isEmpty(formVal)) {
                                formVal = 0;
                            }
                            String formNum = formVal.toString();
                            if (BeanUtils.isNotEmpty(cond.getJudgeVal1())) {
                                script = getCompareScript(cond.getJudgeCon1(), formNum, cond.getJudgeVal1(), true);
                            }
                            if (BeanUtils.isNotEmpty(cond.getJudgeVal2())) {
                                String moreScript = getCompareScript(cond.getJudgeCon2(), formNum, cond.getJudgeVal2(), true);
                                if (StringUtil.isNotEmpty(script)) {
                                    script = script + "&&";
                                }
                                script = script + moreScript;
                            }
                            break;
                        case 2:
                            //字符串
                            if (BeanUtils.isEmpty(formVal)) {
                                formVal = "\"\"";
                            }
                            String formStr = formVal.toString();
                            script = getCompareScript(cond.getJudgeCon1(), formStr, cond.getJudgeVal1(), false);
                            if (StringUtil.isNotEmpty(cond.getJudgeVal2())) {
                                String moreScript = getCompareScript(cond.getJudgeCon2(), formStr, cond.getJudgeVal2(), false);
                                script = script + "&&" + moreScript;
                            }
                            break;
                        case 3:
                            //日期
                            try {
                                String[] formatter = new String[]{"yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "HH:mm:ss", "yyyy-MM-dd HH:mm:00"};
                                Date formDate = null;
                                if (formVal instanceof String) {
                                    formDate = DateUtils.parseDate(formVal.toString(), formatter);
                                }
                                if (formVal instanceof Date) {
                                    formDate = (Date) formVal;
                                }
                                if (formDate == null) {
                                    script = "false";
                                } else {
                                    if (StringUtil.isNotEmpty(cond.getJudgeVal1())) {
                                        Date varDate1 = DateUtils.parseDate(cond.getJudgeVal1(), formatter);
                                        int date1 = formDate.compareTo(varDate1);
                                        Boolean dateResult1 = getDateCompareResult(cond.getJudgeCon1(), date1);
                                        script += dateResult1;
                                    }
                                    if (StringUtil.isNotEmpty(cond.getJudgeVal2())) {
                                        Date varDate2 = DateUtils.parseDate(cond.getJudgeVal2(), formatter);
                                        int date2 = formDate.compareTo(varDate2);
                                        Boolean dateResult2 = getDateCompareResult(cond.getJudgeCon2(), date2);
                                        if (StringUtil.isNotEmpty(script)) {
                                            script = script + "&&";
                                        }
                                        script = script + dateResult2;
                                    }
                                }
                            } catch (ParseException e) {
                                e.printStackTrace();
                            }
                            break;
                        case 4:
                            //字典
                            if (BeanUtils.isEmpty(formVal)) {
                                formVal = "";
                            }
                            String formDic = formVal.toString();
                            String[] vals = cond.getJudgeVal1().split("&&");
                            for (String val : vals) {
                                if (StringUtil.isNotEmpty(script)) {
                                    script += "&&";
                                }
                                script += getCompareScript(cond.getJudgeCon1(), formDic, val, false);
                            }
                            break;
                        case 5:
                            //角色、组织、岗位选择器
                            String[] idName = cond.getJudgeVal1().split("&&");
                            if (idName.length == 2) {
                                String idVal;
                                if ("startUser".equals(cond.getFlowvarKey()) || cond.getFlowvarKey().matches("^.*ID$")) {
                                    idVal = (String) formVars.get(cond.getFlowvarKey());
                                } else {
                                    idVal = (String) formVars.get(cond.getFlowvarKey() + "ID");
                                }
                                if (idVal == null) {
                                    idVal = "";
                                }
                                List<String> list = new ArrayList<>();
                                String[] idVals = idVal.split(",");
                                Collections.addAll(list, idVals);
                                formVal = list;
                                String[] ids = idName[0].split(",");
                                for (String id : ids) {
                                    if (StringUtil.isNotEmpty(script)) {
                                        script += "||";
                                    }
                                    script += getCompareScript(cond.getJudgeCon1(), cond.getFlowvarKey(), id, true);
                                }
                            }
                            break;
                        default:
                            break;
                    }
                    if (StringUtil.isEmpty(script)) {
                        continue;
                    }
                    //执行结果记录到operatorList中
                    Map<String, Object> scriptVars = new HashMap<>(2);
                    scriptVars.put(cond.getFlowvarKey(), formVal);
                    Boolean result = groovyScriptEngine.executeBoolean(script, scriptVars);
                    Map<String, Object> resultMap = new HashMap<>(2);
                    //与运算
                    resultMap.put("operator", cond.getCompType());
                    resultMap.put("result", result);
                    operatorList.add(resultMap);
                }
            }
        }
    }

    /**
     * 获取规则运算结果
     *
     * @param operatorList 操作列表
     * @return 返回结果
     */
    private Boolean executeOperator(List<Map<String, Object>> operatorList) {
        if (operatorList.isEmpty()) {
            return true;
        }
        Boolean returnVal = (Boolean) operatorList.get(0).get("result");
        if (operatorList.size() == 1) {
            return returnVal;
        }
        int size = operatorList.size();
        for (int k = 1; k < size; k++) {
            Map<String, Object> resultMap = operatorList.get(k);
            String operator = resultMap.get("operator").toString();
            if ("or".equals(operator)) {
                //或运算
                returnVal = returnVal || ((Boolean) resultMap.get("result"));
            }
            if ("and".equals(operator)) {
                //与运算
                returnVal = returnVal && ((Boolean) resultMap.get("result"));
            }
        }
        return returnVal;
    }

    /**
     * 获取比较值的脚本
     *
     * @param compare 比较符号
     * @param val1    比较值1
     * @param val2    比较值2
     * @return 脚本
     */
    private String getCompareScript(String compare, String val1, String val2, Boolean isObj) {
        String script;
        if (compare.contains(StringPool.BRACKET)) {
            if (compare.contains(StringPool.NOT_CONTAINS)) {
                if (isObj) {
                    script = "!" + val1 + ".contains" + "('" + val2 + "')";
                } else {
                    script = "!'" + val1 + "'.contains" + "('" + val2 + "')";
                }
            } else {
                if (isObj) {
                    script = val1 + "." + compare.replace("()", "") + "('" + val2 + "')";
                } else {
                    script = "'" + val1 + "'." + compare.replace("()", "") + "('" + val2 + "')";
                }
            }
        } else {
            if (isObj) {
                script = val1 + compare + val2;
            } else {
                script = "'" + val1 + "'" + compare + "'" + val2 + "'";
            }
        }
        return script;
    }

    /**
     * 获取日期的比较结果
     *
     * @param compare 比较运算符号
     * @param d       两个日期的比较结果
     * @return 比较结果
     */
    private Boolean getDateCompareResult(String compare, int d) {
        switch (compare) {
            case "==":
                return d == 0;
            case "!=":
                return d != 0;
            case ">":
                return d == 1;
            case "<":
                return d == -1;
            case ">=":
                return d == 0 || d == 1;
            case "<=":
                return d == 0 || d == -1;
            default:
                return true;
        }
    }

    /**
     * 计算获取用户
     *
     * @param conditionId 条件id
     * @param procInstId 流程实例id
     * @param startUserId 发起人
     * @param preTaskUser 上一步执行人
     * @param vars 流程变量
     * @param isUser 是否是用户
     * @return 用户列表
     */
    public List getExecutors(String conditionId, String procInstId, String startUserId, String preTaskUser, Map<String, Object> vars, boolean isUser) {
        List<BpmNodeUser> nodeUsers = bpmNodeUserDao.getByConditionId(conditionId);
        Set userIdSet = new HashSet();

        for (BpmNodeUser bpmNodeUser : nodeUsers) {
            Set uIdSet;
            if (isUser) {
                uIdSet = getUsersByBpmNodeUser(bpmNodeUser, startUserId, preTaskUser, procInstId, vars);
            } else {
                uIdSet = getByBpmNodeUser(bpmNodeUser, startUserId, preTaskUser, procInstId, vars);
            }

            if (userIdSet.isEmpty()) {
                userIdSet = uIdSet;
            } else {
                userIdSet = computeUserSet(bpmNodeUser.getCompType(), userIdSet, uIdSet);
            }
        }
        return new ArrayList<>(userIdSet);
    }

    /**
     * 直接获取用户。
     *
     * @param bpmNodeUser 用户设置对象
     * @param startUserId 发起人
     * @param preTaskUserId 上一步执行人
     * @param procInstId 流程实例id
     * @param vars 流程变量
     * @return 用户列表
     */
    private Set<SysUser> getUsersByBpmNodeUser(BpmNodeUser bpmNodeUser, String startUserId, String preTaskUserId, String procInstId, Map<String, Object> vars) {
        CalcVars params = new CalcVars(startUserId, preTaskUserId, procInstId, vars);
        IBpmNodeUserCalculation calculation = bpmNodeUserCalculationSelector.getByKey(bpmNodeUser.getAssignType());
        return new HashSet<>(calculation.getExecutor(bpmNodeUser, params));
    }

    /**
     * 取得任务执行人。
     *
     * @param bpmNodeUser 用户设置对象
     * @param startUserId 发起人
     * @param preTaskUserId 上一步执行人
     * @param procInstId 流程实例id
     * @param vars 流程变量
     * @return 执行人列表
     */
    private Set<TaskExecutor> getByBpmNodeUser(BpmNodeUser bpmNodeUser, String startUserId, String preTaskUserId, String procInstId, Map<String, Object> vars) {
        CalcVars params = new CalcVars(startUserId, preTaskUserId, procInstId, vars);
        IBpmNodeUserCalculation calculation = bpmNodeUserCalculationSelector.getByKey(bpmNodeUser.getAssignType());
        if (calculation == null) {
            return new HashSet<>();
        }
        return calculation.getTaskExecutor(bpmNodeUser, params);
    }

    /**
     * 计算两个集合的交集或合集或排除
     *
     * @param computeType 计算类型
     * @param userIdSet   原集合
     * @param newUserSet  新集合
     * @return 用户集合
     */
    public Set computeUserSet(short computeType, Set userIdSet, Set newUserSet) {
        if (newUserSet == null) {
            return userIdSet;
        }
        if (BpmNodeUser.COMP_TYPE_AND == computeType) {
            // 交集
            Set orLastSet = new LinkedHashSet();
            for (Object key : userIdSet) {
                if (newUserSet.contains(key)) {
                    orLastSet.add(key);
                }
            }
            return orLastSet;
        } else if (BpmNodeUser.COMP_TYPE_OR == computeType) {
            userIdSet.addAll(newUserSet);
        } else {
            // 排除
            for (Object newUserId : newUserSet) {
                userIdSet.remove(newUserId);
            }
        }
        return userIdSet;
    }

    /**
     * 根据excution获取人员数据。
     *
     * @param execution         流程实例
     * @param bpmUserConditions 用户条件设置
     * @param preTaskUserId     之前用户id
     * @return
     */
    public List<SysUser> getTaskExecutors(DelegateExecution execution, List<BpmUserCondition> bpmUserConditions, String preTaskUserId) {
        Map<String, Object> processVars = execution.getVariables();
        String procDefId = execution.getProcessDefinitionId();
        String procInstId = execution.getProcessInstanceId();
        String startUserId = (String) processVars.get(BpmConst.StartUser);
        String preTaskExecutorId = "";
        if (preTaskUserId != null) {
            preTaskExecutorId = String.valueOf(preTaskUserId);
        }

        return (List<SysUser>) getExecutorsByConditions(bpmUserConditions, procDefId, procInstId, startUserId, preTaskExecutorId, processVars, true);
    }

    /**
     * 通过流程定义id获取流程设置的抄送人员
     *
     * @param procDefId     流程定义id
     * @param procInstId    流程实例id
     * @param startUserId   消息发送人
     * @param variables     流程变量
     * @param preTaskUserId 上一个任务执行人
     * @return 抄送人员列表
     */
    public List<SysUser> queryCopyUserByProcDefId(String procDefId, String procInstId, String startUserId, Map<String, Object> variables, String preTaskUserId) {
        List<SysUser> userList = new ArrayList<>();
        String parentProcDefId = (String) variables.get(BpmConst.FLOW_PARENT_PROCDEFID);
        List<BpmUserCondition> bpmUserConditions;
        if (StringUtil.isEmpty(parentProcDefId)) {
            bpmUserConditions = bpmUserConditionService.queryCcByProcDefId(procDefId, null);
        } else {
            bpmUserConditions = bpmUserConditionService.queryCcByProcDefId(procDefId, parentProcDefId);
        }

        if (BeanUtils.isEmpty(bpmUserConditions)) {
            return userList;
        }

        userList = getExecutorsByConditions(bpmUserConditions, procDefId, procInstId, startUserId, preTaskUserId, variables, true);
        return userList;
    }

    /**
     * 根据ProceCmd userCondition获取人员数据。
     * @param processCmd 流程扩展对象
     * @param userConditions 用户条件
     * @param preTaskUserId 当前用户
     */
    public List<SysUser> getUserByCondition(ProcessCmd processCmd, List<BpmUserCondition> userConditions, String preTaskUserId) {
        Map<String,Object> processVars = processCmd.getVariables();
        ProcessRun run = processCmd.getProcessRun();
        String actDefId = run.getProcDefId();
        String actInstId = run.getProcInstId();
        String startUserId=(String)processVars.get(BpmConst.StartUser);
        String preTaskExecutorId = "";
        if(preTaskUserId!=null){
            preTaskExecutorId=String.valueOf(preTaskUserId);
        }

        return getExecutorsByConditions(userConditions, actDefId, actInstId, startUserId, preTaskExecutorId, processVars,true);
    }
}
